#include "app.h"
#include <qlabel.h>

#include <qpushbutton.h> 

#include <qlayout.h> 

#include <qtooltip.h> 

#include <qwhatsthis.h> 

#include <qimage.h> 

#include <qpixmap.h> 

#include <ctype.h>

#include <qtopia/qpeapplication.h>

#include <qmessagebox.h>

AppForm::AppForm(QWidget* parent, const char* name, WFlags f) :
    BaseForm(parent,name,f)
{        
    	QPalette pal;
    	QColorGroup cg;
        // 隐藏提示框
        TextLabel1->hide();
	cg.setColor( QColorGroup::Foreground, QColor(0, 255, 0) );
	cg.setColor( QColorGroup::Button, QColor( 90, 176, 75) );
	cg.setColor( QColorGroup::Light, QColor( 139, 255, 118) );
	cg.setColor( QColorGroup::Midlight, QColor( 114, 215, 96) );
	cg.setColor( QColorGroup::Dark, QColor( 45, 88, 38) );
	cg.setColor( QColorGroup::Mid, QColor( 60, 117, 50) );
	cg.setColor( QColorGroup::Text, QColor(0, 255, 0) );
	cg.setColor( QColorGroup::BrightText, white );
	cg.setColor( QColorGroup::ButtonText, QColor(0, 255, 0) );
	cg.setColor( QColorGroup::Base, white );
	cg.setColor( QColorGroup::Background, white );
	cg.setColor( QColorGroup::Shadow, black );
	cg.setColor( QColorGroup::Highlight, QColor( 0, 0, 128) );
	cg.setColor( QColorGroup::HighlightedText, white );


	palette1.setActive( cg );
        palette1.setInactive( cg );
        palette1.setDisabled( cg );

	cg.setColor( QColorGroup::Foreground, QColor(255, 0, 0) );
	cg.setColor( QColorGroup::Button, QColor( 90, 176, 75) );
	cg.setColor( QColorGroup::Light, QColor( 139, 255, 118) );
	cg.setColor( QColorGroup::Midlight, QColor( 114, 215, 96) );
	cg.setColor( QColorGroup::Dark, QColor( 45, 88, 38) );
	cg.setColor( QColorGroup::Mid, QColor( 60, 117, 50) );
	cg.setColor( QColorGroup::Text, QColor(255, 0, 0) );
	cg.setColor( QColorGroup::BrightText, white );
	cg.setColor( QColorGroup::ButtonText, QColor(255, 0, 0) );
	cg.setColor( QColorGroup::Base, white );
	cg.setColor( QColorGroup::Background, white );
	cg.setColor( QColorGroup::Shadow, black );
	cg.setColor( QColorGroup::Highlight, QColor( 0, 0, 128) );
	cg.setColor( QColorGroup::HighlightedText, white );

        palette2.setActive( cg );
        palette2.setInactive( cg );
        palette2.setDisabled( cg );

	cg.setColor( QColorGroup::Foreground, QColor(255, 255, 0) );
	cg.setColor( QColorGroup::Button, QColor( 90, 176, 75) );
	cg.setColor( QColorGroup::Light, QColor( 139, 255, 118) );
	cg.setColor( QColorGroup::Midlight, QColor( 114, 215, 96) );
	cg.setColor( QColorGroup::Dark, QColor( 45, 88, 38) );
	cg.setColor( QColorGroup::Mid, QColor( 60, 117, 50) );
	cg.setColor( QColorGroup::Text, QColor(255, 255, 0) );
	cg.setColor( QColorGroup::BrightText, white );
	cg.setColor( QColorGroup::ButtonText, QColor(255, 255, 0) );
	cg.setColor( QColorGroup::Base, white );
	cg.setColor( QColorGroup::Background, white );
	cg.setColor( QColorGroup::Shadow, black );
	cg.setColor( QColorGroup::Highlight, QColor( 0, 0, 128) );
	cg.setColor( QColorGroup::HighlightedText, white );
	palette3.setActive( cg );
        palette3.setInactive( cg );
        palette3.setDisabled( cg );

        TextLabel1->setPalette( palette1 );


        // 创建画布,画笔,设置背景
        canvas = new MyQCanvas(240,210);
        canvas->setDoubleBuffering(TRUE);
        painter = new QPainter(this);
        canvasArea = new QRect(0,0,canvas->width(),canvas->height());  
        pixImg1 = new QPixmap("/MG2440/scenery480x320.png"); 
        setBackgroundPixmap ( *pixImg1 );

        // 在TabWidget1框中增加新页面, 并在新页面上加组件
        Advance = new QWidget( TabWidget1, "Advance" );
        TabWidget1->insertTab( Advance, tr( "Advance" ) );

	GroupBox4 = new QGroupBox( Advance, "GroupBox4" );
	GroupBox4->setGeometry( QRect( 10, 10, 185, 66 ) ); 
	GroupBox4->setFrameShadow( QGroupBox::Raised );
	GroupBox4->setTitle( tr( "" ) );

	TextLabel3 = new QLabel( GroupBox4, "TextLabel3" );
	TextLabel3->setGeometry( QRect( 10, 35, 49, 23 ) ); 
	QFont TextLabel3_font(  TextLabel3->font() );
	TextLabel3_font.setPointSize( 14 );
	TextLabel3->setFont( TextLabel3_font ); 
	TextLabel3->setText( tr( "Volume" ) );
	
	TextLabel2 = new QLabel( GroupBox4, "TextLabel2" );
	TextLabel2->setGeometry( QRect( 10, 9, 49, 23 ) ); 
	QFont TextLabel2_font(  TextLabel2->font() );
	TextLabel2_font.setPointSize( 14 );
	TextLabel2->setFont( TextLabel2_font ); 
	TextLabel2->setText( tr( "Speed" ) );

	ToolButton1 = new QToolButton( GroupBox4, "ToolButton1" );
	ToolButton1->setGeometry( QRect( 65, 10, 16, 23 ) ); 
	QFont ToolButton1_font(  ToolButton1->font() );
	ToolButton1_font.setPointSize( 14 );
	ToolButton1->setFont( ToolButton1_font ); 
	ToolButton1->setText( tr( "<" ) );

	ToolButton3 = new QToolButton( GroupBox4, "ToolButton3" );
	ToolButton3->setGeometry( QRect( 65, 35, 16, 23 ) ); 
	ToolButton3->setText( tr( "<" ) );

	ProgressBar2 = new QProgressBar( GroupBox4, "ProgressBar2" );
	ProgressBar2->setGeometry( QRect( 80, 35, 80, 23 ) ); 
	ProgressBar2->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)0, (QSizePolicy::SizeType)0, ProgressBar2->sizePolicy().hasHeightForWidth() ) );

	cg.setColor( QColorGroup::Foreground, black );
	cg.setColor( QColorGroup::Button, white );
	cg.setColor( QColorGroup::Light, white );
	cg.setColor( QColorGroup::Midlight, white );
	cg.setColor( QColorGroup::Dark, QColor( 127, 127, 127) );
	cg.setColor( QColorGroup::Mid, QColor( 170, 170, 170) );
	cg.setColor( QColorGroup::Text, black );
	cg.setColor( QColorGroup::BrightText, black );
	cg.setColor( QColorGroup::ButtonText, black );
	cg.setColor( QColorGroup::Base, white );
	cg.setColor( QColorGroup::Background, QColor( 170, 255, 255) );
	cg.setColor( QColorGroup::Shadow, black );
	cg.setColor( QColorGroup::Highlight, QColor( 85, 0, 255) );
	cg.setColor( QColorGroup::HighlightedText, white );
	pal.setActive( cg );
	cg.setColor( QColorGroup::Foreground, black );
	cg.setColor( QColorGroup::Button, white );
	cg.setColor( QColorGroup::Light, white );
	cg.setColor( QColorGroup::Midlight, white );
	cg.setColor( QColorGroup::Dark, QColor( 127, 127, 127) );
	cg.setColor( QColorGroup::Mid, QColor( 170, 170, 170) );
	cg.setColor( QColorGroup::Text, black );
	cg.setColor( QColorGroup::BrightText, black );
	cg.setColor( QColorGroup::ButtonText, black );
	cg.setColor( QColorGroup::Base, white );
	cg.setColor( QColorGroup::Background, QColor( 170, 255, 255) );
	cg.setColor( QColorGroup::Shadow, black );
	cg.setColor( QColorGroup::Highlight, QColor( 85, 0, 255) );
	cg.setColor( QColorGroup::HighlightedText, white );
	pal.setInactive( cg );
	cg.setColor( QColorGroup::Foreground, QColor( 128, 128, 128) );
	cg.setColor( QColorGroup::Button, white );
	cg.setColor( QColorGroup::Light, white );
	cg.setColor( QColorGroup::Midlight, white );
	cg.setColor( QColorGroup::Dark, QColor( 127, 127, 127) );
	cg.setColor( QColorGroup::Mid, QColor( 170, 170, 170) );
	cg.setColor( QColorGroup::Text, black );
	cg.setColor( QColorGroup::BrightText, black );
	cg.setColor( QColorGroup::ButtonText, QColor( 128, 128, 128) );	
	cg.setColor( QColorGroup::Base, white );
	cg.setColor( QColorGroup::Background, QColor( 170, 255, 255) );
	cg.setColor( QColorGroup::Shadow, black );cg.setColor( QColorGroup::Highlight, QColor( 85, 0, 255) );
	cg.setColor( QColorGroup::HighlightedText, white );
	pal.setDisabled( cg );
	ProgressBar2->setPalette( pal );

	ProgressBar2->setMouseTracking( FALSE );
	ProgressBar2->setFocusPolicy( QProgressBar::TabFocus );
        ProgressBar2->setFrameShape( QProgressBar::Panel );
        ProgressBar2->setFrameShadow( QProgressBar::Sunken );
        ProgressBar2->setLineWidth( 2 );
        ProgressBar2->setMargin( 0 );
	ProgressBar2->setMidLineWidth( 0 );
	ProgressBar2->setProgress( 20 );
	ProgressBar2->setCenterIndicator( TRUE );
    	ProgressBar2->setIndicatorFollowsStyle( FALSE );

    	ToolButton4 = new QToolButton( GroupBox4, "ToolButton4" );
    	ToolButton4->setGeometry( QRect( 160, 35, 16, 23 ) ); 
    	ToolButton4->setText( tr( ">" ) );

    	ProgressBar1 = new QProgressBar( GroupBox4, "ProgressBar1" );
    	ProgressBar1->setGeometry( QRect( 80, 10, 80, 23 ) ); 
    	ProgressBar1->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)0, (QSizePolicy::SizeType)0, ProgressBar1->sizePolicy().hasHeightForWidth() ) );

    	cg.setColor( QColorGroup::Foreground, black );
    	cg.setColor( QColorGroup::Button, white );
    	cg.setColor( QColorGroup::Light, white );
    	cg.setColor( QColorGroup::Midlight, white );
    	cg.setColor( QColorGroup::Dark, QColor( 127, 127, 127) );
    	cg.setColor( QColorGroup::Mid, QColor( 170, 170, 170) );
    	cg.setColor( QColorGroup::Text, black );
    	cg.setColor( QColorGroup::BrightText, black );
    	cg.setColor( QColorGroup::ButtonText, black );
    	cg.setColor( QColorGroup::Base, white );
    	cg.setColor( QColorGroup::Background, QColor( 170, 255, 255) );
    	cg.setColor( QColorGroup::Shadow, black );
    	cg.setColor( QColorGroup::Highlight, QColor( 85, 0, 255) );
    	cg.setColor( QColorGroup::HighlightedText, white );
    	pal.setActive( cg );
    	cg.setColor( QColorGroup::Foreground, black );
    	cg.setColor( QColorGroup::Button, white );
    	cg.setColor( QColorGroup::Light, white );
    	cg.setColor( QColorGroup::Midlight, white );
    	cg.setColor( QColorGroup::Dark, QColor( 127, 127, 127) );
    	cg.setColor( QColorGroup::Mid, QColor( 170, 170, 170) );
    	cg.setColor( QColorGroup::Text, black );
    	cg.setColor( QColorGroup::BrightText, black );
    	cg.setColor( QColorGroup::ButtonText, black );
    	cg.setColor( QColorGroup::Base, white );
    	cg.setColor( QColorGroup::Background, QColor( 170, 255, 255) );
    	cg.setColor( QColorGroup::Shadow, black );
    	cg.setColor( QColorGroup::Highlight, QColor( 85, 0, 255) );
    	cg.setColor( QColorGroup::HighlightedText, white );
    	pal.setInactive( cg );
    	cg.setColor( QColorGroup::Foreground, QColor( 128, 128, 128) );
    	cg.setColor( QColorGroup::Button, white );
    	cg.setColor( QColorGroup::Light, white );
    	cg.setColor( QColorGroup::Midlight, white );
    	cg.setColor( QColorGroup::Dark, QColor( 127, 127, 127) );
    	cg.setColor( QColorGroup::Mid, QColor( 170, 170, 170) );
    	cg.setColor( QColorGroup::Text, black );
   	cg.setColor( QColorGroup::BrightText, black );
    	cg.setColor( QColorGroup::ButtonText, QColor( 128, 128, 128) );
    	cg.setColor( QColorGroup::Base, white );
    	cg.setColor( QColorGroup::Background, QColor( 170, 255, 255) );
    	cg.setColor( QColorGroup::Shadow, black );
    	cg.setColor( QColorGroup::Highlight, QColor( 85, 0, 255) );
    	cg.setColor( QColorGroup::HighlightedText, white );
    	pal.setDisabled( cg );

    	ProgressBar1->setPalette( pal );

    	ProgressBar1->setMouseTracking( FALSE );
    	ProgressBar1->setFocusPolicy( QProgressBar::TabFocus );
    	ProgressBar1->setFrameShape( QProgressBar::Panel );
    	ProgressBar1->setFrameShadow( QProgressBar::Sunken );
    	ProgressBar1->setLineWidth( 2 );
    	ProgressBar1->setMargin( 0 );
    	ProgressBar1->setMidLineWidth( 0 );
    	ProgressBar1->setProgress( 20 );
    	ProgressBar1->setCenterIndicator( TRUE );
    	ProgressBar1->setIndicatorFollowsStyle( FALSE );

    	ToolButton2 = new QToolButton( GroupBox4, "ToolButton2" );
    	ToolButton2->setGeometry( QRect( 160, 10, 16, 23 ) ); 
    	ToolButton2->setText( tr( ">" ) );

    	// signals and slots connections
    	connect( ToolButton1, SIGNAL( clicked() ), this, SLOT( decSpeed() ) );
    	connect( ToolButton2, SIGNAL( clicked() ), this, SLOT( incSpeed() ) );
    	connect( ToolButton3, SIGNAL( clicked() ), this, SLOT( decVolume() ) );
    	connect( ToolButton4, SIGNAL( clicked() ), this, SLOT( incVolume() ) );

        // radio button group panel
        ModeButtonGroup = new QButtonGroup( Advance, "ModeButtonGroup" );
        ModeButtonGroup->setGeometry( QRect( 10, 90, 185, 66 ) );  
        ModeButtonGroup->setFrameShadow( QButtonGroup::Raised );
        ModeButtonGroup->setTitle( tr( "Mode" ) );
        ModeButtonGroup->setRadioButtonExclusive(TRUE);
        ModeButtonGroup->setFocusPolicy( QButtonGroup::NoFocus );

        // radio button
        NormalModeButton = new QRadioButton( ModeButtonGroup, "NormalModeButton" );
        NormalModeButton->setGeometry( QRect( 15, 18, 95, 19 ) ); 
        NormalModeButton->setText( tr( "Normal" ) );
        NormalModeButton->setChecked(TRUE);
        NormalModeButton->setFocusPolicy( QRadioButton::TabFocus );

        // radio button
        AutoModeButton = new QRadioButton( ModeButtonGroup, "AutoModeButton" );
        AutoModeButton->setGeometry( QRect( 15, 38, 95, 19 ) ); 
        AutoModeButton->setText( tr( "Auto" ) );
        AutoModeButton->setChecked(FALSE);
        AutoModeButton->setFocusPolicy( QRadioButton::TabFocus );
     
        connect( ModeButtonGroup, SIGNAL(clicked(int)), this, SLOT(changeMode(int)) );
        loadMode();

    	// tab order
    	setTabOrder( PAUSE_KEY, ProgressBar1 );
    	setTabOrder( ProgressBar1, ProgressBar2 );
    	setTabOrder( ProgressBar2, NormalModeButton);
        setTabOrder( NormalModeButton, AutoModeButton );

        // -------------------------------------------------------

        widgetMain = this;  // 保存了AppForm对象指针
        musicDir = new QDir("/MG2440/music");
        legalSingleKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789[]\\/;\',.` -="; 

        // 创建一个音乐对象
        music = new MyMusic();  

        // 加载配置文件,初始化游戏参数 
        if (TRUE != loadConfigFile())  initConfig();
        config();

        // 文件列表框添加乐曲文件
        ListDir(musicDir);    

	int interval = BLOCK_WIDTH + BLOCK_SPACE_X; 
        int x = BLOCK_X;
        int y = BLOCK_Y;

        rects[0] = new QRect( x,y,BLOCK_WIDTH, BLOCK_HEIGHT );
        rects[1] = new QRect( x+interval,y,BLOCK_WIDTH, BLOCK_HEIGHT );  
        rects[2] = new QRect( x+interval*2,y,BLOCK_WIDTH, BLOCK_HEIGHT ); 
        rects[3] = new QRect( x+interval*3,y,BLOCK_WIDTH, BLOCK_HEIGHT ); 
        rects[4] = new QRect( x+interval*4,y,BLOCK_WIDTH, BLOCK_HEIGHT ); 
        rects[5] = new QRect( x+interval*5,y,BLOCK_WIDTH, BLOCK_HEIGHT ); 
        rects[6] = new QRect( x+interval*6,y,BLOCK_WIDTH, BLOCK_HEIGHT ); 

        
        // timer1 控制在画布上添加/移动/删除新的音符方块, 及给出游戏GOOD/MISS/FAIL提示 
	timer1 = new QTimer(this);
        connect( timer1, SIGNAL(timeout()), this, SLOT(timer1Proc()) );

        // 琴键按下后的提示时间超时处理.
	timer2 = new QTimer(this);
        connect( timer2, SIGNAL(timeout()), this, SLOT(timer2Proc()) );


        // timer3决定音乐播放时间提示的刷新
	timer3 = new QTimer(this);
        connect( timer3, SIGNAL(timeout()), this, SLOT(timer3Proc()) );
        
        // timer4决定等待多长时间后显示成绩
	timer4 = new QTimer(this);
        connect( timer4, SIGNAL(timeout()), this, SLOT(timer4Proc()) );


        // 创建1个循环指针元素队列, 队列中元素为音符块指针
        queue = new LoopQueue(BLOCK_QUEUE_MAXSIZE);

        // 记录了FAIL,MISS,GOOD提示的次数
        reportFailCount = 0;
        reportGoodCount = 0;  
        reportMissCount = 0;  

        paintFlag = FALSE;

}  

AppForm::~AppForm()
{  

        // 保存游戏的配置参数
        saveConfigFile();

}



/*
    Sets the mode - Normal, Auto
*/

void AppForm::changeMode(int m)
{
   if (m == ModeButtonGroup->id(NormalModeButton))
   {
      gameMode = NORMALMODE;

       // 定义音符块允许被击落的边界, 当音符块下边沿碰到该水平线时, 
        // 音符块变黄色,允许被击落
        hitHorizontalLine = BLOCK_Y_MAX2;
   }
   else if (m == ModeButtonGroup->id(AutoModeButton))
   {
     gameMode = AUTOMODE;
       // 定义音符块允许被击落的边界, 当音符块下边沿碰到该水平线时, 
        // 音符块变黄色,允许被击落
        hitHorizontalLine = BLOCK_Y_MAX1;
   }
}

void AppForm::loadMode(void)
{
   if (NormalModeButton->isChecked () == TRUE)   
   {
       gameMode = NORMALMODE;

       // 定义音符块允许被击落的边界, 当音符块下边沿碰到该水平线时, 
        // 音符块变黄色,允许被击落
        hitHorizontalLine = BLOCK_Y_MAX2;

   }
   else  if (AutoModeButton->isChecked () == TRUE)
   { 
        gameMode = AUTOMODE;

       // 定义音符块允许被击落的边界, 当音符块下边沿碰到该水平线时, 
        // 音符块变黄色,允许被击落
        hitHorizontalLine = BLOCK_Y_MAX1;
   }
} 


// buttonFlagInit()初始化数组buttonFlag[7]为全0
// 数组buttonFlag[7]将分别记录7个琴键是否被按下
void AppForm::buttonFlagInit()
{
   for (unsigned char i = 0; i <7; i++)
   {
      buttonFlag[i] = 0; 
   }
}


// index取值1~7, 对应7个琴键 
void AppForm::buttonproc(unsigned char index)
{
      // 如果当前画布的音轨中有音符块, 则记录琴键被按下
      // 否则, 就不必记录
      if (queue->isEmpty() != TRUE)
      {
         // 记录琴键被按下
         buttonFlag[index-1]++; 
      }


      // 控制发音
      blockSignals ( TRUE );   
      audio.play_note(index);
      blockSignals ( FALSE ); 
}

void AppForm::button1proc()
{
   if (gameMode == AUTOMODE)  return;
   buttonproc(1);
}


void AppForm::button2proc()
{
   if (gameMode == AUTOMODE)  return;
   buttonproc(2);
}

void AppForm::button3proc()
{
   if (gameMode == AUTOMODE)  return;
   buttonproc(3);
}

void AppForm::button4proc()
{
   if (gameMode == AUTOMODE)  return;
   buttonproc(4);
}


void AppForm::button5proc()
{
   if (gameMode == AUTOMODE)  return;
   buttonproc(5);
}

void AppForm::button6proc()
{
   if (gameMode == AUTOMODE)  return;
   buttonproc(6);
}


void AppForm::button7proc()
{
   if (gameMode == AUTOMODE)  return;
   buttonproc(7);
}




// 把当前目录下符合过滤条件的文件和目录添加到文件列表框中
void AppForm::ListDir( QDir *dir)
{

   disconnect( ListBox1, SIGNAL( currentChanged(QListBoxItem*) ), this, SLOT( currentChangedFileItem() ) );

   // 设置文件过滤条件以及文件排序
   dir->setNameFilter("*.vos;*.mid"); // 设置文件和目录过滤条件
   dir->setSorting ( QDir::Name | QDir::DirsFirst );  // 设置按名字排序, 且目录在前
   dir->setMatchAllDirs(TRUE);  // 设置过滤条件仅仅影响文件,不影响目录

   // 清空文件列表框
   ListBox1->clear();

   // 获取目录下所有目录信息和符合过滤条件的所有文件信息,
   // 把Dir下所有目录和符合过滤条件的所有文件, 添加到文件列表框中
   const QFileInfoList *files = dir->entryInfoList();
   
   if ( files ) 
   {
      QFileInfoListIterator it( *files );
      QFileInfo * fi;
      while( (fi=it.current()) != 0 ) 
      {
          ++it;

          if ( TRUE == fi->isDir() )
          {
             if (fi->fileName() != ".")  
             {
                ListBox1->insertItem("[ "+fi->fileName()+" ]");
             } 
          }
          else if ( TRUE == fi->isFile() )
          {
             ListBox1->insertItem(fi->fileName()); 
          } 
      }
      
      // 把文件列表框第1项作为当前项, 并设置高亮
      ListBox1->setCurrentItem (0);
      ListBox1->setSelected ( 0, TRUE);     
   }

   connect( ListBox1, SIGNAL( currentChanged(QListBoxItem*) ), this, SLOT( currentChangedFileItem() ) );
   
}



void AppForm::languageChange()
{

}




//在音乐简谱中，记录音的高低和长短的符号，叫做音符。
//而用来表示这些音的高低的符号，是用七个阿拉伯数字作为标记，
//它们的写法是：1 2 3 4 5 6 7 读法为:do re mi fa so la si（多 来 米 发 梭 拉 西）。
//我们用键盘的七个按键对应音乐简谱中的七个音符1 2 3 4 5 6 7.
//本函数监听键盘事件并处理.
void AppForm::keyPressEvent(QKeyEvent* k)
{
    int c;
    int value;
    QWidget* w = focusWidget();
    QString wstr = w->className();

    // 琴键处理

    c=  k->key();
    if ( wstr != "QLabel")
    if ( (k->stateAfter() == 0) && (((uint)c) < 128) )
    {
        if (c == soundkey.at(0))   // 琴键1, 控制发"多"音
        {
            PushButton1->animateClick ();   	
        }

        else if (c == soundkey.at(1))  // 琴键2, 控制发"来"音
        {
            PushButton2->animateClick ();   
            return;	
        }   
        else if (c == soundkey.at(2))  // 琴键3, 控制发"米"音
        {
            PushButton3->animateClick ();   	
        }   
        else if (c == soundkey.at(3))  // 琴键4, 控制发"发"音
        {
            PushButton4->animateClick ();   	
        }   
        else if (c == soundkey.at(4))  // 琴键5, 控制发"梭"音
        {
            PushButton5->animateClick ();   	
        }   
        else if (c == soundkey.at(5))  // 琴键6 控制发"拉"音
        {
            PushButton6->animateClick ();   	
        }   
        else if (c == soundkey.at(6))  // 琴键7 控制发"西"音
        {
            PushButton7->animateClick ();   	
        }   
        return;
    }



    switch(k->key())
    {
        // 左方向键用于设定减少速度或音量
        case Qt::Key_Left:
            if (ProgressBar1 == w)
            {      
               // 设定乐曲新速度
               value = getSpeed(); 
               if ( value )  setSpeed( value - 10 );
            }
            else if (ProgressBar2== w)
            {
               value = getVolume();
               if (value) 
                  setVolume( value - 10 );
            }
           
            return; 

        // 右方向键用于设定增加速度或音量
        case Qt::Key_Right:
            if (ProgressBar1 == w)
            {
               // 设定乐曲新速度
               value = getSpeed(); 
               if (value < 100) 
                  setSpeed( value + 10 );
            }
            else if (ProgressBar2== w)
            {
               value = getVolume();
               if (value < 100) 
                  setVolume( value + 10 );
            }
         
            return; 

        // 上方向键
        case Qt::Key_Up :

            return; 

        // 下方向键
        case Qt::Key_Down :

            return; 

        // ENTER键
        case Qt::Key_Return :    
           widgetMain->setFocus();    
           widgetMain->setActiveWindow();
           return; 

        // Backspace键
        case Qt::Key_Backspace :
           widgetMain->setFocus();    
           widgetMain->setActiveWindow();
           return;
   
        default :
            break;
    }
  
    QString str = Hotkey(k);

    // 用户定制Hot key
    if (music->running != RUNNING)
    {
       // 玩家定制单键码热键
       if ( (DO_KEY == w) || (RE_KEY == w) || (MI_KEY == w) || (FA_KEY == w) || (SO_KEY == w)
          || (LA_KEY == w) || (SI_KEY == w) || (START_KEY == w) || (PAUSE_KEY == w) ) 
       {
           // 玩家定制热键, 完后退出
           customHotkey(w, str);
           return;
       }
    }

    // 控制键处理
    if (str == startkey)  startButton->animateClick ();   
    else if (str == pausekey)  pauseButton->animateClick ();   

}



int  AppForm::getSpeed()
{
   return ProgressBar1->progress();
}


// 设定乐曲速度参数
void AppForm::setSpeed ( int v)
{

   if (v < 0) v = 0;
   else if (v>100) v = 100;

   v = v - (v%10);
   ProgressBar1->setProgress(v);

   // 记录当前进度条上的乐曲速度参数
   speedValue = v;
}





void AppForm::decSpeed()
{
   int v = ProgressBar1->progress();
   if (v)
   {  
      v = v - 10;
      if (v < 0) v = 0; 
      setSpeed(v);  // 设定乐曲速度参数
   }

}

void AppForm::incSpeed()
{
   int v = ProgressBar1->progress();
   if (v < 100)
   {  
      v = v + 10;
      v = v - (v%10);
      setSpeed(v);  // 设定乐曲速度参数
   }
}


int AppForm::getVolume ( )
{
   return ProgressBar2->progress();
}


void AppForm::setVolume ( int v)
{
   uint volume = 0;

   if (v < 0) v = 0;
   else if (v>100) v = 100;
   ProgressBar2->setProgress(v);

   // 记录当前进度条上的乐曲音量参数
   volumeValue = v;   

   // 更新乐曲音量
   music->setVolume(volume);    
}



void AppForm::decVolume()
{
   int v = ProgressBar2->progress();
   if (v)
   {  
      v = v - 10;
      if (v < 0) v = 0; 
      setVolume(v); // 更新乐曲音量
   }
}

void AppForm::incVolume()
{
   int v = ProgressBar2->progress();
   if (v < 100)
   {  
      v = v + 10;
      v = v - (v%10);
      setVolume(v); // 更新乐曲音量
   }
}


// 把用户按下的热键转换成字符串的形式
// 如:  用户按下F2, 则该函数输出为"F2"
//      用户按下CTRL+A,则该函数输出"CTRL+A"
//      用户按下B键,则该函数输出"B"  
QString AppForm::Hotkey (QKeyEvent* k)
{
    QString str = "";

    // 处理玩家定制的热键为按下了CTRL的情况
    if (k->stateAfter() == Qt::ControlButton)
    {
       str = "CTRL+";
    } 
    else if ( k->stateAfter() != 0) return str;


    // 处理玩家定制的热键为F1~F12的情况
    int v = k->key();
    switch(v)
    {
       case Qt::Key_F1:
          str.append("F1");
          break;
       case Qt::Key_F2:
          str.append("F2");
          break;
       case Qt::Key_F3:
          str.append("F3");
          break;
       case Qt::Key_F4:
          str.append("F4");
          break;
       case Qt::Key_F5:
          str.append("F5");
          break;
       case Qt::Key_F6:
          str.append("F6");
          break;
       case Qt::Key_F7:
          str.append("F7");
          break;
       case Qt::Key_F8:
          str.append("F8");
          break;
       case Qt::Key_F9:
          str.append("F9");
          break;
       case Qt::Key_F10:
          str.append("F10");
          break;
       case Qt::Key_F11:
          str.append("F11");
          break;
       case Qt::Key_F12:
          str.append("F12");
          break;
       default:
          if (v < 128)
          {
             if (legalSingleKey.contains(v))
             {
                str.append(v);
             }
          }
          break;
    } 

    return str;
}


void AppForm::customHotkey (QWidget* w, QString str)
{
    char i;
    
    i = -1;
    if (DO_KEY == w)  i = 0;
    else if (RE_KEY == w) i = 1;
    else if (MI_KEY == w) i = 2;
    else if (FA_KEY == w) i = 3;
    else if (SO_KEY == w) i = 4;
    else if (LA_KEY == w) i = 5;
    else if (SI_KEY == w) i = 6; 
    else if ((START_KEY == w) || (PAUSE_KEY == w))
    // 玩家定制Start,Pause热键
    {         
        // 如果热键合法, 则更新Start,Pause热键文本提示框内容
        if (legalControlKey(str) == TRUE)
        {
           if (str != " ") 
              ((QLabel*)w)->setText(str); 
           else 
              ((QLabel*)w)->setText("SPACE"); 
        }
        else return;
   
        // 保存Start,Pause热键
        if (START_KEY == w)  startkey = str;
        else pausekey = str;
       
        // 显示更新后的热键
        w->update();

        return; 
    }
    else return;
 

    // -----------------------------------------
    // 玩家定制7个琴键(单键码热键)
    // -----------------------------------------
    // 若k为非法单键码, 则退出
    if (str.length() != 1) return;
    if (legalSingleKey.contains(str) == 0)  return;

    if (str != " ") 
       ((QLabel*)w)->setText(str); 
    else 
       ((QLabel*)w)->setText("SPACE"); 

    AppForm::soundkey.replace(i, 1, str);
       
    w->update();
}



void QWidget::mouseDoubleClickEvent ( QMouseEvent * e )
{
    QString str(this->className());
    if ( (str == "QProgressBar") || (str == "QLabel") )
    {
        ((QPalette &)(this->palette())).setColor( QPalette::Active, QColorGroup::Light, Qt::red );
        ((QPalette &)(this->palette())).setColor( QPalette::Active, QColorGroup::Dark, Qt::red );  
        this->update();    
        this->setFocus();
        this->setActiveWindow();
    }
}


void QWidget::focusOutEvent ( QFocusEvent * fEvent)
{
    ((QPalette &)(this->palette())).setColor( QPalette::Active, QColorGroup::Light, Qt::white );
    ((QPalette &)(this->palette())).setColor( QPalette::Active, QColorGroup::Dark, Qt::black );  
    this->update();
}

void QWidget::focusInEvent ( QFocusEvent * fEvent)
{
    if (! ((fEvent->reason() == QFocusEvent::Tab) || (fEvent->reason() == QFocusEvent::Mouse)) )
         return;

    QString str(this->className());
    if ( (str == "QProgressBar") || (str == "QLabel") || (str == "QPushButton") )
    {
        ((QPalette &)(this->palette())).setColor( QPalette::Active, QColorGroup::Light, Qt::red );
        ((QPalette &)(this->palette())).setColor( QPalette::Active, QColorGroup::Dark, Qt::red );  
        this->update();
    }
    if (str == "QRadioButton") 
    {  
        ((QRadioButton *)this)->setChecked(TRUE);
    }
}



// timer1 控制在画布上添加/移动/删除新的音符方块, 及给出游戏GOOD/MISS/FAIL提示 
void AppForm::timer1Proc()
{
    MyQCanvasRectangle *block;  // 音符块指针
    unsigned char track;
    unsigned int i;
    QRect r[BLOCK_QUEUE_MAXSIZE];
    unsigned int r_len = 0;

    // 分析歌曲的乐谱, 在界面上增加一个个方块指示乐谱中音符次序
    while(1)
    {
       if (music->decide() == TRUE)
       // 增加一个方块来体现一个新音符的到来 
       // 新块的下边沿与动画可视区域的上边沿重叠(新块向下移动一步距离, 新块便可见!)
       {
                    
          track = music->getSyllable();  // 确定当前音符块所在的音轨编号1~7
          block = addBlock(rects[track-1]);  // 增加一个方块代表一个音符

          queue->insertItem(block);  // 把新音符块的指针增加到对应的队列中.

          // 到来的新音节的节拍若不为0,则退出循环体;
          // 否则,意味着新音节紧连着下一个新音节, 所以在界面中增加多个方块
          // 来体现多个新音节的同时到达.
          if (music->getMeter())  
          {
             break;  
          }
       } 
       else  break;    // 在当前音节节拍没完情况下, 新音节不会到来
                        // 即不会在界面上增加一个方块来体现一个新音节的到来
                        // 所以退出循环体.
    } 

    // 当前音节的节拍时间减少
    music->decMeter(); 


    // 若当前画面上有音符块存在, 则在画布上移动/删除音符方块, 
    // 及根据玩家按键情况给出游戏GOOD/MISS/FAIL提示 
    for ( i = 0; (block = queue->item(i)) != NULL; )
    {
       // 已搜索到的(画面上的)有效音符块个数
       i++;        

       // 记录画布上需要重绘成背景的已经改变的区域 
       r[r_len++] = block->rect();

       // block音符块向下移动一步长距离
       moveBlock(block);  

       // 自动模式下虚拟按下了琴键
       track = block->getSyllable();  // 确定当前音符块所在的音轨编号1~7
       if (gameMode == AUTOMODE)  
       {      
          if (TRUE == AutoModeBlockHitCheck(block))
          // 允许自动撞击当前音符块?
          {
             buttonproc(track);   // 琴键按下处理
          }
       }

       // 1) 用户按下当前音轨的琴键, 撞击了block指向的音符块, 若当音符
       //    块允许撞击,则删除该音符块,并给用户"GOOD"提示, 否则, 给用户"FAIL"提示
       // 2) 用户没有按下当前音轨的琴键, 当音符块已经下落到琴键上边沿以下(不可见), 
       //    则自动删除该音符块, 并给用户"MISS"提示
       if (buttonFlag[track-1])
       // 若用户按下当前音轨的琴键, 撞击了block指向的音符块
       {
          // 清对应琴键clicked状态 
          buttonFlag[track-1] = 0;  

          if (block->getHitEnable())
          // 若音符块允许玩家撞击, 并玩家撞击了
          {
             // 确定玩家击打音符块后, 下一个音符块对应的hitHorizontalLine
             // 若玩家在1个节拍内(与音符块高度值对应)完成击打操作, hitHorizontalLine不变.
             // 否则, hitHorizontalLine下移, 这样是为了保证在下一个音符块
             // 下边沿碰到hitHorizontalLine时变成黄色块, 成为新的可被玩家撞击的块.
             // 当然, hitHorizontalLine <= BLOCK_Y_MAX1 (游戏画面活动区域的下边界)
             // 隐含的信息是: 一旦玩家没有在1个节拍内完成相应琴键操作,要击打下一音符块,
             // 还需要等(meter-1)个节拍, 才会有新音符块撞击到hitHorizontalLine.
             // (meter为block对象的属性)
             if (block->rect().y() > hitHorizontalLine)
             {
                hitHorizontalLine = block->rect().y();
                if (hitHorizontalLine >= BLOCK_Y_MAX1) 
                {
                   hitHorizontalLine = BLOCK_Y_MAX1;
                }
             }

             // 删除个音符块
             delete block;  
             block = NULL;   // 后面还要用到block, 防止block成为野指针
 
             // 从队列中剔除被删除音符块的指针
             queue->deleteItem();

             // 画面中可见的音符块数量减少1个
             i--;       

             if (gameMode != AUTOMODE)  // 自动播放音乐不需要提示
             {
                 // 给用户"GOOD"提示
                 //TextLabel1->setText("<font color=\"#00A000\">GOOD</font>");
                 //TextLabel1->setText("<font color=\"#00ff00\">GOOD</font>");
                 TextLabel1->setPalette( palette1 );
                 TextLabel1->setText("GOOD");
                 TextLabel1->show();
                 reportGoodCount++;

                 //重置显示提示的时间, 最长(HINT_MAXTIME) ms
                 if(timer2->isActive() == TRUE)  timer2->stop();
                 timer2->start(HINT_MAXTIME, TRUE);
             }     
          }
          else
          // 音符块不允许玩家撞击, 玩家却撞击了
          {
             // 给用户"FAIL"提示
             //TextLabel1->setText("<font color=\"#ff0000\">FAIL</font>");
             TextLabel1->setPalette( palette2 );
             TextLabel1->setText("FAIL");
             TextLabel1->show();
             reportFailCount++;

             //重置显示提示的时间, 最长(HINT_MAXTIME) ms
             if(timer2->isActive() == TRUE)  timer2->stop();
             timer2->start(HINT_MAXTIME, TRUE);
          }
       }
       else if (blockLife(block) != TRUE)
       // 若block指向的有效音符块出了画面活动区域
       {
          // 因为被删音符块是被自动删除的, 非用户按对应琴键后删除的,
          // 所以要提示MISS, 提示用户在音符块消失前,没有按下对应琴键的动作
          TextLabel1->setPalette( palette3 );
          TextLabel1->setText("MISS");
          //TextLabel1->setText("<font color=\"#ffff00\">MISS</font>");
          TextLabel1->show();
          reportMissCount++;

          //重置显示提示的时间, 最长(HINT_MAXTIME) ms
          if(timer2->isActive() == TRUE)  timer2->stop();
          timer2->start(HINT_MAXTIME, TRUE);



       }

       // 不管玩家按没按键, 只要block指向的有效音符块出了画面活动区域,
       // 就删除该块. 之所以在下面单独这样处理,是为了避免极端情况下,
       // 没有及时删除出了画面活动区域的音符块
       if (block != NULL)  // 若block指向有效音符块
       {
          if (blockLife(block) != TRUE)
          // 若音符块已经下落到画面活动区域外了(琴键上边沿以下),
          // 则自动删除该块,并提示用户MISS
          {
             // 玩家MISS一个音符块, 则hitHorizontalLine下移到画面活动区域下边界
             hitHorizontalLine = BLOCK_Y_MAX1;

             // 删除个音符块
             delete block;  
             block = NULL;   // 防止block成为野指针
 
             // 从队列中剔除被删除音符块的指针
             queue->deleteItem();

             // 画面中可见的音符块数量减少1个
             i--;    



          }
       }  

       if (block != NULL)
       {
          // 更新该音符块的hitEnable属性, stepCount属性
          blockHitEnableUpdate(block);  
          if (block->getHitEnable())
          {
              //记录了该块从允许被击打到消失, 持续了多少步的时间
              block->incStepCount();
          }
       }
 
    }  // for ( i ...

    // 音轨上无音符块,玩家却按了对应的琴键,提示FAIL
    for ( track = 1; track <= 7; track++)
    {
       if (buttonFlag[track-1])
       {
          // 清对应琴键clicked状态 
          buttonFlag[track-1] = 0;  

          // 给用户"FAIL"提示
          //TextLabel1->setText("<font color=\"#ff0000\">FAIL</font>");
          TextLabel1->setPalette( palette2 );
          TextLabel1->setText("FAIL");
          TextLabel1->show();
          reportFailCount++;

          //重置显示提示的时间, 最长(HINT_MAXTIME) ms
          if(timer2->isActive() == TRUE)  timer2->stop();
          timer2->start(HINT_MAXTIME, TRUE);
       }
    }
   
    // 已访问处理了乐谱的所有音节, 且画面中已经无音符块,
    // 这表示游戏结束.
    if ( (music->isOver() == TRUE) && (i == 0) )
    {
       timer1->stop();    	// 关定时器
       timer3->stop();     
       //SONGTIME->setText("");
       music->running = STOP; // 游戏结束
       startButton->setText("Start"); 
       pauseButton->setText("Pause"); 
       if (gameMode != AUTOMODE)  // 自动播放音乐不需要提示
       {
           timer4->start(500, TRUE);  // 0.5秒后显示成绩报告
       }
    }
    for (i = 0; i < r_len; i++)
    {
       update(r[i]);
    }
    if (i) paintFlag = TRUE;
}



// 琴键按下后的提示时间超时处理.
void AppForm::timer2Proc() 
{ 
   TextLabel1->clear();
   TextLabel1->hide();
}


void AppForm::displayMusicTime()
{
   char buf[12];

   unsigned char minute, second;
   unsigned long t;

   // 若乐曲文件没加载成功, 直接退出
   if (music->isVaild() != TRUE)  return;

   memset(buf, 0, sizeof(buf));
 
   t = music->getEslapedTime()/1000;

   minute = (t/60)%60;
   second = t%60;

   memset(buf, 0, sizeof(buf));
   buf[0] = (minute/10)+'0';
   buf[1] = (minute%10)+'0';
   buf[2] = ':';
   buf[3] = (second/10)+'0';  
   buf[4] = (second%10)+'0';
   buf[5] = '/';

   t = music->getTime()/1000;
   minute = (t/60)%60;
   second = t%60;

   buf[6] = (minute/10)+'0';
   buf[7] = (minute%10)+'0';
   buf[8] = ':';
   buf[9] = (second/10)+'0';  
   buf[10] = (second%10)+'0';
   buf[11] = 0;

   SONGTIME->setText("<font color=\"#ff0000\">"+QString(buf)+"</font>");
   SONGTIME->update();

}


// 音乐播放时间提示
void AppForm::timer3Proc() 
{ 

   // 显示乐曲总时间和播放时间
   displayMusicTime();

}



// 统计显示成绩
void AppForm::timer4Proc() 
{ 
      scoreReport();
      music->clearEslapedTime();
      displayMusicTime();
}


// 文件列表当前选项发生了改变
void AppForm::currentChangedFileItem()
{
    // 读出当前选中的文件或目录名字
    QString str = ListBox1->currentText();
    if (str.isNull())    return;      // 若为空, 退出
    else if (str.at(0) != '[')
    {
       if(music->running != STOP)  return;

       // 加载新乐谱并解码 
       str = musicDir->absFilePath(str);
       music->load( str.ascii() );  

       // 设置乐曲速度
       music->setSpeed(speedValue); 

       // 显示乐曲总时间和播放时间
       displayMusicTime();

    }
    else
    {
       SONGTIME->setText("");
       SONGTIME->update();
    }
}



// 双击或按下Enter键打开文件列表选项
void AppForm::selectFileItem()
{
    
    // 读出当前选中的文件或目录名字
    QString str = ListBox1->currentText();
    if (str.isNull())    return;      // 若为空, 退出
    else if (str.at(0) == '[')
    // 若为目录, 把当前目录下符合过滤条件的文件和目录添加到文件列表框中
    {
        str.remove(0, 2); // 去掉开始2个字符"[ "
        str.remove(str.length()-2, 2); // 去掉结尾2个字符" ]"
        if (str == "..")
        {
           // 如果是根目录, 点击".."就不返回上一级目录 
           if (musicDir->isRoot () == musicDir->absPath()) return;
        }

        // 暂存父目录名字
        QString dirName = musicDir->dirName();

        musicDir->cd(str);  // 进入指定目录
        ListDir(musicDir);  // 把当前目录下符合过滤条件的文件和目录添加到文件列表框中

        // 文件列表框条目的高亮设置
        if (str == "..")
        {
            dirName = "[ " + dirName +" ]";
            for (uint i = 0; i < ListBox1->count(); i++)
            {
                if (dirName == ListBox1->text(i))
                {
                    // 选中文件列表框当前项, 设置高亮
                    ListBox1->setCurrentItem (i);
                    ListBox1->setSelected ( i, TRUE);    
                }
            }          
        }
        return;
    }    
}

void AppForm::startGame()
{
    if (startButton->text() == "Start")
    {
      music->running = START; // 游戏开始
    }
    else
    {
      music->running = STOP; // 游戏结束
    }
    gameControl(); 
} 

void AppForm::pauseGame()
{
    if (pauseButton->text() == "Pause")
    {
      if ( music->running != RUNNING)  return;
      music->running = PAUSE; // 游戏暂停
    }
    else
    {
      if ( music->running != PAUSE)  return;
      music->running = CONTINUE; // 游戏继续
    }
    gameControl(); 
}


void AppForm::paintEvent(QPaintEvent *event) 
{  
 
    MyQCanvasRectangle *block;  // 音符块指针 

    unsigned int i;

   
    // 绘图与显示
    if ( paintFlag != TRUE)  return;
    paintFlag = FALSE;
    for ( i = 0; (block = queue->item(i)) != NULL; i++)
    {     
       if ( block->getHitEnable() != TRUE )
       // 当音符块不允许被撞击,音符块的颜色为指蓝靛色
       {
          //QColor color(128, 255, 255);  // 淡青色
          //QColor color(200, 200, 0);    // 棕黄色
          QColor color(130, 100, 200);    // 蓝靛色
          painter->setPen(QPen( color ));
          painter->setBrush(QBrush( color ));  
       }
       else 
       // 当音符块允许被撞击,音符块的颜色为指黄色
       { 
          painter->setPen(QPen( Qt::yellow ));
          painter->setBrush(QBrush( Qt::yellow )); 
       }

       // 绘图并显示
       block->drawShape(*painter);
    }

   TextLabel1->update();
}



// 控制开始/结束/暂停/继续游戏.
void AppForm::gameControl()
{
   QString str;

   if (music->running == STOP)
   // 游戏要结束?
   {  
      startButton->setText("Start"); 
      pauseButton->setText("Pause"); 
      if (timer1->isActive() == TRUE) timer1->stop();    	// 关定时器
      if (timer3->isActive() == TRUE) timer3->stop();    	// 关定时器
      if (timer4->isActive() == TRUE) timer4->stop();    	// 关定时器
      return;
   }
   else if (music->running == PAUSE)
   // 游戏要暂停?
   {  
      if (timer1->isActive() == TRUE) timer1->stop();  // 关定时器
      if (timer3->isActive() == TRUE) timer3->stop();  // 歌曲播放时间暂时不变化
      music->timeBase += music->time.elapsed ();    // 保存暂停前的时间 
      pauseButton->setText("Continue");
      return;
   }
   else if (music->running == CONTINUE)
   // 游戏要继续?
   {   
      if (speedValue == 0) return;
      pauseButton->setText("Pause");  
   }
   else if (music->running == START)
   // 游戏要开始?
   {  
      music->running = STOP;
      music->setSpeed(speedValue); 
      if (music->getSpeed() == 0)  return;  


      // 若成绩还没统计显示出来, 就等成绩显示后再重启动游戏
      if(timer4->isActive() == TRUE)  return;

      // 读当前选中的乐谱文件名
      str = ListBox1->currentText();
      if (str.isNull())  return;

      // 若为目录, 则退出
      if (str.at(0) == '[')  return;


      // 加载新乐谱并解码 
      str = musicDir->absFilePath(str);
      music->load( str.ascii() );  

      // 若加载的乐谱不正确, 这里报错提示.
      if (TRUE != music -> isVaild())
      {
         // 这里添加出错提示代码
         return; 
      }
      else
      {
         // 音符块指针队列初始化
         queue->init();

         // 记录了FAIL,MISS,GOOD提示的次数
         reportFailCount = 0;
         reportGoodCount = 0;  
         reportMissCount = 0; 

         // 定义音符块允许被击落的边界, 当音符块下边沿碰到该水平线时, 
         // 音符块变黄色,允许被击落
         hitHorizontalLine = BLOCK_Y_MAX2;

         startButton->setText("Stop");
         pauseButton->setText("Pause"); 

         // 重新load模式 
         loadMode();

         update(*canvasArea);

         music->timeBase = 0;
      }
   }

   // buttonFlagInit()初始化数组buttonFlag[7]为全0
   // 数组buttonFlag[7]将分别记录7个琴键是否被按下
   // 这些状态用于控制玩家弹琴键后系统提示"MISS", "GOOD", "FAIL"
   buttonFlagInit();

   // 显示乐曲总时间和播放时间
   displayMusicTime();

   // 启动周期ms定时(音符块下移一步长距离需要的时间), 游戏开始.
   timer1->start( music->getSpeed() );

   // 设置每隔50ms刷新乐曲播放时间
   timer3->start( 50 );  	

   music->running = RUNNING;
  
   music->time.restart();
}




// 查询代表音符的块状态
bool AppForm::blockLife(MyQCanvasRectangle* block)
{
    return (block->rect().y() <= (BLOCK_Y_MAX1+1) ? TRUE : FALSE);
}



// 检查Block下边沿是否在撞击区域内(hitHorizontalLine水平线之下区域)
bool AppForm::blockEdgeCheck(MyQCanvasRectangle* block, int borderline)
{
    return ((block->rect().y() + (BLOCK_HEIGHT+BLOCK_SPACE_Y)) >= (borderline) ? TRUE : FALSE);
}


// 更新音符块的hitEnable属性
void AppForm::blockHitEnableUpdate(MyQCanvasRectangle* block)
{
    if (block == NULL) return;

    MyQCanvasRectangle* tmp;


    // 检查block的hitEnable 属性
    if (block->getHitEnable() == TRUE)  return;    

    // 检查Block下边沿是否在撞击区域内(hitHorizontalLine水平线之下)
    if (TRUE != blockEdgeCheck(block, hitHorizontalLine))  return; 

    // 只要block指向的音符块在画面中离底部最近(与第一个块平齐),就允许被撞击
    // 如此判断也满足2个或多个音符一齐发音的情况
    tmp = queue->first();
    if (block->rect().y() == tmp->rect().y())
    {
        // 设置标志,允许该块被玩家琴键撞击
        block->setHitEnable();
    }
}

bool AppForm::AutoModeBlockHitCheck(MyQCanvasRectangle *block)
{
    // 检查Block下边沿是否在"自动模式下音符块自动被击落的下边界"之下)
    return  blockEdgeCheck(block, BLOCK_Y_MAX3);

}


// 控制代表音符的块向下移动一步长距离, 步长距离受music的step属性制约
void AppForm::moveBlock(MyQCanvasRectangle* block) 
{
    double y = block->rect().y() + (BLOCK_HEIGHT+BLOCK_SPACE_Y)/(music->getStep());

    block->move( block->rect().x(), y);
}



// 读文件数据
bool AppForm::readFile(char *filename, char * &buf, int &len)
{
    FILE *fp;

    fp = fopen(filename, "r");
    if (fp == NULL)  return FALSE;


    // 获取文件长度, 分配数据缓冲区
    if (0 != fseek(fp, 0, SEEK_END))
    {
       fclose(fp);
       return FALSE;
    }
    len = ftell(fp);
    buf = new char[len+1];
    buf[len] = 0;

    // 定位读文件起始位置
    if (0 != fseek(fp, 0, SEEK_SET))
    {
       fclose(fp);
       return FALSE;
    }

    // 读整个文件
    fread(buf, sizeof(char), len, fp);

    // 关闭文件
    fclose(fp);

    return TRUE;
}


// 检查字符串代表的键码是否合法
// 合法的键码: 
// 1) 单键: ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789[]\\/;\',.` -=
// 2) F1~F12
// 3) Ctrl+合法单键或F1~F12
bool AppForm::legalControlKey(QString str)
{
   const QString& cstr1 = legalSingleKey;
   const QString cstr2 = "F1F2F3F4F5F6F7F8F9F10F11F12"; 
   QString tmp;
   QChar ch;
   unsigned char i, len;

   str = str.upper();
   len = str.length();
   switch(len)
   {
      case 1:
         // str 为单键码?
         if (cstr1.contains(str))  return TRUE;
         break;
      case 2:
      case 3:      
         // str 为F1~F12?
         if (cstr2.contains(str) && (TRUE == str.startsWith("F")))  return TRUE;
         break;
      case 6:
         if (TRUE == str.startsWith("CTRL+"))
         // str 为CTRL+单键ASCII码 
         {
           i = len - 1;
           ch = str.at(i);           
           if (cstr1.contains(ch))  return TRUE;
         }
         break;
      case 7:
         if (TRUE == str.startsWith("CTRL+"))
         // str 为CTRL+(F1~F12)
         {
           i = len - 2;
           tmp = str.mid( i, 2);
           if (cstr2.contains(tmp) && (TRUE == tmp.startsWith("F")))  return TRUE;
         }
         break;
      default:
         break;
   }
   return FALSE;  
}


// 解释config.ini文件
bool AppForm::parseFile(char * string)
{
    QString str(string), str1, str2, str3, str4, str5;
    int i, j, k,m,n;
    str1 = "soundkey";
    str2 = "startkey";
    str3 = "pausekey";
    str4 = "speed";
    str5 = "volume";

    // 格式调整, 便于字符串处理
    str = str.simplifyWhiteSpace ();
    str.append(" ");

    // config.ini格式检查, 检查字符串str是否包含了字符串str1,str2,str3,str4,str5
    i = str.find(str1, 0, FALSE);
    if (i == -1)  return FALSE;
    j = str.find(str2, 0, FALSE);
    if (j == -1)  return FALSE;
    k = str.find(str3, 0, FALSE);
    if (k == -1)  return FALSE;
    m = str.find(str4, 0, FALSE);
    if (m == -1)  return FALSE;
    n = str.find(str5, 0, FALSE);
    if (n == -1)  return FALSE;

    // 得到7个琴键码     
    i = str.find("\"", i, FALSE);
    if (i == -1)  return FALSE;
    str1 = str.mid( i+1, 7 ); 

    // 得到Start键的键值 
    i = str.find("\"", j, FALSE);
    if (i == -1)  return FALSE;
    j = str.find("\"", i+1, FALSE);
    if (j == -1)  return FALSE;
    i = i + 1;
    str2 = str.mid( i, j-i);
    if (TRUE != legalControlKey(str2))  return FALSE;

    // 得到Pause键的键值 
    i = str.find("\"", k, FALSE);
    if (i == -1)  return FALSE;
    k = str.find("\"", i+1, FALSE);
    if (k == -1)  return FALSE;
    i = i + 1;
    str3 = str.mid( i, k-i);
    if (TRUE != legalControlKey(str3))  return FALSE;

    // 得到speed参数值 
    i = str.find("=", m, FALSE);
    if (i == -1)  return FALSE;
    i++;
    while (str.at(i) == ' ')  { i++; }
    m = str.find(" ", i, FALSE);
    if (m == -1)  return FALSE;
    str4 = str.mid( i, m-i);

    // 得到volume参数值 
    i = str.find("=", n, FALSE);
    if (i == -1)  return FALSE;
    i++;
    while (str.at(i) == ' ')  { i++; }
    n = str.find(" ", i, FALSE);
    if (n == -1)  return FALSE;
    str5 = str.mid( i, n-i);


    // speed, volume格式转换
    bool OK;
    speedValue = str4.toInt( &OK, 10 );
    if (OK != TRUE)  return FALSE;
    volumeValue = str5.toInt( &OK, 10 );   
    if (OK != TRUE)  return FALSE;

    soundkey = str1.upper();
    startkey = str2.upper();
    pausekey = str3.upper();

    return TRUE;
}

// 加载配置文件config.ini并解码, 提取配置参数. 
bool AppForm::loadConfigFile(void)
{
    char* buf = NULL;
    int len;
    bool  ret = FALSE;

    if ( TRUE == readFile("/MG2440/config.ini", buf, len))
    {
        if (len < 150)
          ret = parseFile(buf);
    }

    if (buf != NULL)  delete buf; // 释放分配的内存空间

    return ret;
}


// 保存配置参数到配置文件config.ini
bool AppForm::saveConfigFile()
{
    fp = fopen("/MG2440/config.ini", "w");
    if (fp == NULL)  return FALSE;

    QString  str = QString( "soundkey = \""+soundkey+"\"\n"
                +"startkey = \""+startkey+"\"\n"
                +"pausekey = \""+pausekey+"\"\n"          
                +"speed = %1\nvolume = %2")
                .arg( speedValue, 0, 10 )
                .arg( volumeValue, 0, 10 );

    // 写文件
    fwrite(str.ascii(), sizeof(char), str.length(), fp);

    // 关闭文件
    fclose(fp);

    fp = NULL;

    return TRUE;
}


// 如果配置文件config.ini 加载失败, 以如下参数为缺省参数
void AppForm::initConfig()
{
   soundkey = "1234567";
   startkey = "CTRL+S";
   pausekey = "CTRL+P";
   speedValue = 20;
   volumeValue = 20; 
}


// 以目前的配置参数配置图形界面上相应的组件
void AppForm::config()
{
    QChar ch;
    unsigned char i;
    QLabel * label = NULL;

    setSpeed(speedValue);
    setVolume(volumeValue);
    
    for ( i = 0; i < 7; i++)
    {
       switch(i)
       {
          case 0:
             label = DO_KEY;
             break;
          case 1:
             label = RE_KEY;
             break;
          case 2:
             label = MI_KEY;
             break;
          case 3:
             label = FA_KEY;
             break;
          case 4:
             label = SO_KEY;
             break;
          case 5:
             label = LA_KEY;
             break;
          case 6:
             label = SI_KEY;
             break;
          default:
             break;
       }
       ch = soundkey.at(i).upper();
       if (ch == ' ')
          label->setText( "SPACE" );
       else
          label->setText( ch );
    }
    START_KEY->setText( startkey );
    PAUSE_KEY->setText( pausekey );    
}



// 在画面中增加一个音符块
MyQCanvasRectangle* AppForm::addBlock(QRect* r) 
{
   MyQCanvasRectangle* p = new MyQCanvasRectangle( *r, canvas,
          music->getSyllable(), music->getMeter(), music->getStep(), music->getCount()/2);
   return p;
}


// 报告成绩
void AppForm::scoreReport()
{
   QString str;
   double d = ((double)(reportGoodCount*100))/(reportGoodCount+reportFailCount+reportMissCount);
   int overcnt = (reportGoodCount+reportFailCount+reportMissCount)-(music->size/2);
   str = QString( "\n\nGOOD: %1 times      \nFAIL:   %2 times      \nMISS:  %3 times      \nOver: %4 times\nCorrect Ratio: %5%").arg( reportGoodCount, 0, 10 )
                .arg( reportFailCount, 0, 10 )
                .arg( reportMissCount, 0, 10 )
                .arg( overcnt, 0, 10 )
                .arg( d, 0, 'f', 2 );
   QMessageBox::about(NULL, "Score Report", str);
}



// 该函数产生一个决策, 其返回值决定了是否通过在界面上增加一个方块来体现一个新音节的到来 
bool MyMusic::decide()
{
   if ((meter == 0) && (count < size))
   {
      syllable = data[count];
      meter = data[count+1];
      stepCount = 0;
      count += 2;
      return TRUE;   // 找到一个有效的新音节
   }
   return FALSE;
}



// 读音乐文件数据
bool MyMusic::read()
{
    FILE *fp;
    int len;
    char *buf;


    fp = fopen(name, "r");
    if (fp == NULL)  return FALSE;

    // 获取文件长度, 分配数据缓冲区
    if (0 != fseek(fp, 0, SEEK_END))
    {
       fclose(fp);
       return FALSE;
    }
    len = ftell(fp);
    buf = new char[len];

    // 定位读文件起始位置
    if (0 != fseek(fp, 0, SEEK_SET))
    {
       fclose(fp);
       return FALSE;
    }

    // 读整个文件
    fread(buf, sizeof(char), len, fp);

    // 关闭文件
    fclose(fp);

    size = len;
    data = buf;
    return TRUE;
}


// 函数decode()判别乐谱文件格式(文本)的有效性, 并解码.
// 音乐文件格式(文本):
//	{5,1},{5,2},{6,1},{5,1},{1,1},{7,2},
//	{5,1},{5,2},{6,1},{5,1},{2,2},{1,2},
//	{5,1},{5,2},{5,1},{3,1},{1,1},{7,1},{6,2},
//	{4,1},{4,1},{3,1},{1,1},{2,1},{1,1}
//   其中2个字符'{' ',' 之间数字(只有1个)表示一个音节,音节取值范围1~7
//   其中2个字符',' '}' 之间数字(只有1~3个)表示一个节拍,节拍取值范围0~255
//
//乐谱文件解码后格式(二进制): 
//	05 01 05 02 06 01 05 01 01 07 02
//	..............................................................
//	..............................................................
//	..............................................................
bool MyMusic::decode()
{
    int i, j, k = 0;
    char flag = 0;
    char t[4];
    int len = size;
    char *buf = data;
        
    if ((len == 0) || (buf == NULL)) return FALSE;     

    for (j = 0, i = 0; (i < len) && (buf[i] != 0); i++)
    {
        // 判别是否有非法字符出现
        if ( !( (buf[i] == '{') || (buf[i] == '}') || (buf[i] == ' ')  || (buf[i] == 0x0A) || (buf[i] == 0x0D)
            || (buf[i] == ',') || (buf[i] == 0x09) || ((buf[i] >= '0')&&(buf[i] <= '9')) ) )
        {
           return FALSE;   
        }

        // 判别特殊字符出现次序是否合乎要求.
        // 4个特殊字符 '{'  ','  '}'  ','  必须是按次序循环出现的
        // flag 意义:
        //   flag = 0 初始状态 或 表示 ',' 已经紧接着3个字符'{' ',' '}'后出现
        //   flag = 1 表示字符 '{' 已经正常出现
        //   flag = 2 表示字符 ',' 已经紧接着'{'出现
        //   flag = 3 表示字符 '}' 已经紧接着2个字符'{' ','后出现
        switch(buf[i])
        {
           case '{':
              // 第一个收到'{' 或在4个字符'{'  ','  '}'  ',' 之后收到'{' , 或者在
              // 3个字符'{'  ','  '}' 之后收到'{' , 都是合法的.
              if ((flag == 0) || (flag == 3))
              {
                 flag = 1;  
                 j = 0;  // 数字字符计数器初始化
              }
              else  return FALSE;
              break;

           case ',':
              if (flag == 1) 
              {
                 flag = 2; 

                 // 字符 '{' 与 ',' 之间只能有1个数字字符
                 // 该数字字符表示音乐的单个音节(1~7)
                 if (j != 1)  return FALSE;
                 if (t[0] > '7' || t[0] < '1')  return FALSE;

                 buf[k++] = t[0] - '0';  // 缓存音节数据
                 j = 0;  // 重置数字字符计数器
              }
              else if(flag == 3) 
              {
                 flag = 0;
                 j = 0;  // 重置数字字符计数器
              }
              else return FALSE;
              break;

           case '}':
              if (flag == 2) 
              {
                 flag = 3; 
             
                 // 字符 '{' 与 ',' 之间必须有1~3个数字字符
                 // 该数字字符串表示音乐的单个音节的节拍(取值: 0~255)
                 if ((j == 0) || (j > 3))  return FALSE;

                 // 我们得到节拍值  
                 t[j] = 0;  // 纯数字字符串末尾'\0'   
                 buf[k++] = atoi(t);
                 j = 0;
              }
              else return FALSE;
              break;

           default:
              break;
        }

        // 查找并缓存数字字符串
        switch(flag)
        {
           case 0:  // 初始状态 或 表示 ',' 已经紧接着3个字符'{' ',' '}'后出现
              break;

           case 1:  // 表示字符 '{' 已经正常出现
           case 2:  // 表示字符 ',' 已经紧接着'{'后出现

              // 如果字符'{' ','之间出现数字, 就缓存, 数字不能超过3个
              if ((buf[i] >= '0') && (buf[i] <= '9'))
              {
                 t[j++] = buf[i]; 
                 if (j > 3) return FALSE;
              }
              break;         
        
           case 3:  // 表示字符 '}' 已经紧接着2个字符'{' ','后出现
              break;

           default:
              break;
              
        }    
              
   }

   // 记录解码后的有效数据长度
   if ( (k == 0) || ((k%2) != 0) ) return FALSE;
   size = k;   
   return TRUE;
}



// 加载新的乐谱文件并解码	
void MyMusic::load(const char *filename)
{
        if (filename == NULL)  return;
        QString str(filename);
        int i = str.findRev('.');
        if (i == -1)  return;
        QString ext = str.right(str.length()-i);

        // MyMusic类对象数据成员初始化
        syllable = 0;   // 音节
        meter = 0;     // 节拍
        count =  0;    // 计数
        step = 10;
        stepCount = 0; 
        running = STOP;  // 音乐执行状态
        residualTime = totalTime;
        eslapedTime = 0;
        timeBase = 0;

        // 若已经加载了, 就不要重加载了, 直接退出!
        if (name != NULL)
        {
           if ((strcmp(filename, name) == 0) && (isVaild() == TRUE)) 
             return;   
        }

        // MyMusic类对象数据成员初始化
        if (data != NULL)  delete data;  // 卸载之前的乐谱文件数据
        if (name != NULL) delete name;  // 释放原有文件名(含相对路径)所占的内存
        data = NULL;
        name = NULL;
        vaild = FALSE;  		 // 表示当前乐谱无效!
        name = new char[strlen(filename) + 1];  // 重新分配内存
        strcpy(name, filename);    // 记录乐谱文件名字

        // 读音乐文件
        if (FALSE == read())  return;

        if (ext == ".vos")   
        {  
           // 解码*.vos乐谱文件
           if (FALSE == decode())  return;

           residualTime = getTime();
           vaild = TRUE;  // 加载并解码OK, 当前乐谱有效!
        }
        else if (ext == ".mid")   
        {
           // 解码*.mid乐谱文件
        }  
}


// 设定以ms为单位乐曲速度(乐曲速度=步速=1/10节拍)
void MyMusic::setSpeed(unsigned int speedValue)  
{

   switch(speedValue)
   {
      case 0: 
         speed = 0;
         break;
      case 10:
         speed = 100;  // 100ms
         break;
      case 20:
         speed = 90;  // 90ms
         break;
      case 30:
         speed = 80;  // 80ms
         break;
      case 40:
         speed = 70;
         break;
      case 50:
         speed = 60;
         break;
      case 60:
         speed = 50;
         break;
      case 70:
         speed = 40;
         break;
      case 80:
         speed = 30;
         break;
      case 90:
         speed = 20;
         break;
      case 100:
         speed = 10;
         break;
      default:
         break;
   }

}

